package logic

import (
	"common/global"
	"common/initialize"
	"context"
	"encoding/json"
	"equipment_server/model"
	equipment "equipment_server/proto"
	"errors"
	"fmt"
	"log"

	"github.com/google/uuid"
	"go.mongodb.org/mongo-driver/bson"

	"common/utils"
	"time"
)

// BindDevice implements equipment.EquipmentServer.BindDevice
// 处理设备绑定RPC请求。
// 这个方法会发起一个异步的绑定流程：发送MQTT指令给设备，然后立即返回一个初步响应。
// 最终的绑定结果需要通过监听设备的MQTT响应来确认和处理。
func (e *EquipmentServer) BindDevice(ctx context.Context, req *equipment.BindDeviceRequest) (*equipment.BindDeviceResponse, error) {
	// 打印日志，记录收到的绑定请求的详细信息
	log.Printf("收到用户 %d 绑定设备 %s 的RPC请求", req.GetUserId(), req.GetDeviceId())

	// 创建响应结构体，并初始化默认值（通常是失败状态）
	resp := &equipment.BindDeviceResponse{
		Success: false,      // 默认设置为失败
		Message: "绑定请求处理失败", // 默认失败消息
		// 为本次 RPC 请求生成一个唯一的请求ID，用于跟踪设备异步响应
		RequestId: uuid.New().String(),
	}

	// --- 前置条件检查 ---

	// 获取 MongoDB 中存储设备绑定关系的集合
	bindingsCollection := global.MonGoDatabase.Collection("device_bindings")

	// 合并查询条件，一次查出所有相关绑定记录
	filter := bson.M{
		"$or": []bson.M{
			{"user_id": req.GetUserId()},
			{"device_id": req.GetDeviceId()},
		},
	}

	cursor, err := bindingsCollection.Find(ctx, filter)
	if err != nil {
		log.Printf("查询绑定信息失败: %v", err)
		resp.Message = "内部错误，查询绑定信息失败"
		return resp, errors.New("database query error")
	}
	defer cursor.Close(ctx)

	var (
		userHasOtherDevice   bool
		deviceHasOtherUser   bool
		userHasThisDevice    bool
		existingUserDeviceId string
	)

	for cursor.Next(ctx) {
		var binding model.DeviceBinding
		if err := cursor.Decode(&binding); err != nil {
			continue
		}
		if binding.UserID == req.GetUserId() && binding.DeviceID == req.GetDeviceId() {
			userHasThisDevice = true
		} else if binding.UserID == req.GetUserId() {
			userHasOtherDevice = true
			existingUserDeviceId = binding.DeviceID
		} else if binding.DeviceID == req.GetDeviceId() {
			deviceHasOtherUser = true
		}
	}

	if userHasOtherDevice {
		resp.Message = fmt.Sprintf("您已绑定设备 %s，请先解绑后再绑定新设备", existingUserDeviceId)
		log.Printf("用户 %d 绑定设备 %s 失败: %s", req.GetUserId(), req.GetDeviceId(), resp.Message)
		return resp, nil
	}
	if deviceHasOtherUser {
		resp.Message = fmt.Sprintf("设备 %s 已被其他用户绑定", req.GetDeviceId())
		log.Printf("用户 %d 绑定设备 %s 失败: %s", req.GetUserId(), req.GetDeviceId(), resp.Message)
		return resp, nil
	}
	if userHasThisDevice {
		resp.Success = true
		resp.Message = fmt.Sprintf("您已绑定设备 %s", req.GetDeviceId())
		log.Printf("用户 %d 绑定设备 %s (已绑定): %s", req.GetUserId(), req.GetDeviceId(), resp.Message)
		return resp, nil
	}

	// --- 发送异步绑定请求给设备 ---

	// 获取 MongoDB 中存储待处理绑定请求的临时集合
	// 集合名称为 "pending_binding_requests"，对应 model.PendingBindingRequest
	pendingRequestsCollection := global.MonGoDatabase.Collection("pending_binding_requests")

	// 4. 记录待处理的绑定请求信息到临时集合
	// 这条记录用于后续设备通过 MQTT 响应时，能够找到对应的请求并处理
	pendingReq := model.PendingBindingRequest{
		RequestID:   resp.GetRequestId(), // 使用本次RPC生成的唯一请求ID
		UserID:      req.GetUserId(),     // 绑定发起的用户ID
		DeviceID:    req.GetDeviceId(),   // 目标设备ID
		RequestType: "bind",              // 请求类型为绑定
		Timestamp:   time.Now(),          // 记录请求发起的时间
	}

	// 将待处理请求信息插入到临时集合中
	_, err = pendingRequestsCollection.InsertOne(ctx, pendingReq)
	if err != nil {
		// 如果保存待处理请求失败，说明数据库有问题，这是一个内部错误
		log.Printf("保存用户 %d 绑定设备 %s 的待处理请求失败 (RequestID: %s): %v", req.GetUserId(), req.GetDeviceId(), resp.GetRequestId(), err)
		resp.Message = "内部错误，保存请求失败"
		return resp, errors.New("database insert error") // 返回内部错误
	}
	log.Printf("用户 %d 绑定设备 %s 的待处理请求信息已保存 (RequestID: %s)", req.GetUserId(), req.GetDeviceId(), resp.GetRequestId())

	// 5. 构建并发送 MQTT 绑定请求给设备
	// MQTT 消息 payload 需要包含足够的信息供设备处理，例如 request_id、user_id 等
	// payload 的具体格式需要和设备端协商一致
	mqttPayload := map[string]interface{}{
		"request_id":   resp.GetRequestId(), // 包含请求ID，设备响应时必须带回以便匹配
		"user_id":      req.GetUserId(),     // 绑定发起的用户ID
		"device_id":    req.GetDeviceId(),   // 目标设备ID
		"request_type": "bind",              // 指令类型为绑定
		// 根据需要添加其他信息，例如设备的配置信息、用户名称等
	}

	// 将 payload 结构体编码为 JSON 格式的字节数组，用于 MQTT 消息体
	payloadBytes, err := json.Marshal(mqttPayload)
	if err != nil {
		log.Printf("编码 MQTT payload 失败 (RequestID: %s): %v", resp.GetRequestId(), err)
		resp.Message = "内部错误，构建MQTT消息失败"
		// 如果编码失败，待处理请求已记录，可以在后续的清理任务中处理
		return resp, errors.New("json marshal error") // 返回内部错误
	}

	// 定义 MQTT 主题，用于向特定设备发送指令
	// 通常主题结构会包含设备ID，以便消息路由到正确的设备
	topic := fmt.Sprintf("device/%s/requests", req.GetDeviceId()) // 示例主题：device/<device_id>/requests

	// 使用全局的 MQTT 客户端实例发布消息
	// QoS (Quality of Service) 设置为 1 (至少一次送达)，确保消息能够到达设备
	// Retained 设置为 false
	mqttClient := initialize.InitMQTT()
	token := mqttClient.Publish(topic, 1, false, payloadBytes)

	// Publish 是一个非阻塞操作，token.Wait() 会阻塞当前goroutine直到消息成功发送或发送失败
	token.Wait() // 等待发布操作完成

	// 检查发布操作是否出错
	if token.Error() != nil {
		log.Printf("向设备 %s 发送绑定请求失败 (RequestID: %s): %v", req.GetDeviceId(), resp.GetRequestId(), token.Error())
		resp.Message = "发送绑定请求到设备失败，请检查设备连接或稍后再试"
		// 如果发送 MQTT 失败，待处理请求已记录，可以在后续的清理任务中处理
		return resp, errors.New("mqtt publish error") // 返回内部错误
	}

	log.Printf("已向设备 %s 发送绑定请求 (RequestID: %s). 等待设备异步响应...", req.GetDeviceId(), resp.GetRequestId())

	// 6. 返回初步成功响应
	// 这个响应表示服务器已成功接收并处理了绑定请求的初步阶段，已向设备发送指令
	// 最终的绑定结果（设备是否同意）将通过设备异步返回的 MQTT 响应来确定和处理
	resp.Success = true
	resp.Message = "绑定请求已发送，请等待设备确认"

	// 添加分布式锁
	lockKey := fmt.Sprintf("bind_device_lock:%d:%s", req.GetUserId(), req.GetDeviceId())
	locked, err := utils.SetRedisNx(lockKey, "1", 10*time.Second)
	if err != nil {
		resp.Message = "系统繁忙，请稍后重试"
		return resp, err
	}
	if !locked {
		resp.Message = "操作过于频繁，请稍后再试"
		return resp, nil
	}
	defer utils.DelRedis(lockKey)

	return resp, nil // 返回初步成功响应
}
